using System;
using System.Collections.Generic;
using System.Threading;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.IO;
using System.Text;
using System.ServiceModel.Channels;
using System.Runtime.Serialization.Formatters.Binary;
using SmartBoard.Core.BaseObjects;

namespace SmartBoard.Core.Networking
{
    /// <summary>
    /// The connection host.
    /// </summary>
    public abstract class BaseSocketConnectionHost : Disposable, IBaseSocketConnectionHost
    {
        #region Fields

        private long _connectionId;

        private readonly CallbackThreadType _callbackThreadType;

        //----- Enumerates the connections and creators!
        private ReaderWriterLockSlim _socketConnectionsSync;
        private Dictionary<long, BaseSocketConnection> _socketConnections;
        private List<BaseSocketConnectionCreator> _socketCreators;

        //----- Waits for objects removing!
        private ManualResetEvent _waitCreatorsDisposing;
        private ManualResetEvent _waitConnectionsDisposing;
        private ManualResetEvent _waitThreadsDisposing;

        //----- Check idle timer!
        private Timer _idleTimer;

        #endregion

        #region Constructor

        protected BaseSocketConnectionHost(HostType hostType, CallbackThreadType callbackThreadtype, ISocketService socketService, DelimiterType delimiterType, byte[] delimiter, int socketBufferSize, int messageBufferSize, int idleCheckInterval, int idleTimeOutValue)
        {
            HostType = hostType;
            _connectionId = 1000;

            _socketConnectionsSync = new ReaderWriterLockSlim();

            _socketConnections = new Dictionary<long, BaseSocketConnection>();
            _socketCreators = new List<BaseSocketConnectionCreator>();
            BufferManager = BufferManager.CreateBufferManager(0, messageBufferSize);
            SocketService = socketService;

            _waitCreatorsDisposing = new ManualResetEvent(false);
            _waitConnectionsDisposing = new ManualResetEvent(false);
            _waitThreadsDisposing = new ManualResetEvent(false);

            IdleCheckInterval = idleCheckInterval;
            IdleTimeOutValue = idleTimeOutValue;

            _callbackThreadType = callbackThreadtype;
            DelimiterType = delimiterType;

            Delimiter = delimiter;
            DelimiterEncrypt = new byte[] { 0xFE, 0xDC, 0xBA, 0x98, 0xBA, 0xDC, 0xFE };

            MessageBufferSize = messageBufferSize;
            SocketBufferSize = socketBufferSize;
        }

        #endregion

        #region Public Properties

        public int SocketBufferSize
        {
            get;
            private set;
        }

        public int MessageBufferSize
        {
            get;
            private set;
        }

        public byte[] DelimiterEncrypt
        {
            get;
            private set;
        }

        public byte[] Delimiter
        {
            get;
            private set;
        }

        public DelimiterType DelimiterType
        {
            get;
            private set;
        }

        public ISocketService SocketService
        {
            get;
            private set;
        }

        public int IdleCheckInterval
        {
            get;
            private set;
        }

        public int IdleTimeOutValue
        {
            get;
            private set;
        }

        public HostType HostType
        {
            get;
            private set;
        }
        #endregion

        #region Internal Properties

        internal BufferManager BufferManager
        {
            get;
            private set;
        }
        #endregion

        #region Methods

        /// <summary>
        /// Starts the base host.
        /// </summary>
        public void Start()
        {
            if (IsDisposed) return;

            var loopSleep = 0;

            foreach (var creator in _socketCreators)
            {
                creator.Start();
                ThreadUtils.LoopSleep(ref loopSleep);
            }

            if ((IdleCheckInterval > 0) && (IdleTimeOutValue > 0))
            {
                _idleTimer = new Timer(CheckSocketConnections);
            }

            if (_idleTimer != null)
            {
                _idleTimer.Change(IdleCheckInterval, IdleCheckInterval);
            }
        }

        /// <summary>
        /// Stop the base host.
        /// </summary>
        public virtual void Stop()
        { }

        /// <summary>
        /// Stop the host creators.
        /// </summary>
        protected void StopCreators()
        {
            if (!IsDisposed)
            {
                //----- Stop Creators!
                var creators = GetSocketCreators();
                if (creators != null)
                {
                    _waitCreatorsDisposing.Reset();
                    var loopCount = 0;
                    foreach (var creator in creators)
                    {
                        try
                        {
                            creator.Stop();
                        }
                        finally
                        {
                            RemoveCreator(creator);
                            creator.Dispose();
                            ThreadUtils.LoopSleep(ref loopCount);
                        }
                    }

                    if (creators.Length > 0)
                    {
                        _waitCreatorsDisposing.WaitOne(Timeout.Infinite, false);
                    }
                }
            }
        }

        protected void StopConnections()
        {
            if (!IsDisposed)
            {
                //----- Stop Connections!
                BaseSocketConnection[] connections = GetSocketConnections();

                if (connections != null)
                {
                    _waitConnectionsDisposing.Reset();
                    int loopSleep = 0;
                    foreach (BaseSocketConnection connection in connections)
                    {
                        connection.BeginDisconnect();
                        ThreadUtils.LoopSleep(ref loopSleep);
                    }

                    if (connections.Length > 0)
                    {
                        _waitConnectionsDisposing.WaitOne(Timeout.Infinite, false);
                    }
                }
            }
        }

        private void FireOnConnected(BaseSocketConnection connection)
        {
            if (IsDisposed) return;
            if (!connection.Active) return;
            
            switch (connection.EventProcessing)
            {
                case EventProcessing.User:
                    SocketService.OnConnected(new ConnectionEventArgs(connection));
                    break;
                case EventProcessing.Encrypt:
                    OnConnected(connection);
                    break;
                case EventProcessing.Proxy:
                    OnConnected(connection);
                    break;
            }
        }

        private void FireOnSent(BaseSocketConnection connection, bool sentByServer)
        {
            if (IsDisposed) return;
            if (!connection.Active) return;

            switch (connection.EventProcessing)
            {
                case EventProcessing.User:
                    SocketService.OnSent(new MessageEventArgs(connection, null, sentByServer));
                    break;
                case EventProcessing.Encrypt:
                    OnSent(connection);
                    break;
                case EventProcessing.Proxy:
                    OnSent(connection);
                    break;
            }
        }

        private void FireOnReceived(BaseSocketConnection connection, byte[] buffer)
        {
            if (IsDisposed) return;
            if (!connection.Active) return;
            
            switch (connection.EventProcessing)
            {
                case EventProcessing.User:
                    SocketService.OnReceived(new MessageEventArgs(connection, buffer, false));
                    break;
                case EventProcessing.Encrypt:
                    OnReceived(connection, buffer);
                    break;
                case EventProcessing.Proxy:
                    OnReceived(connection, buffer);
                    break;
            }
        }

        private void FireOnDisconnected(ISocketConnection connection)
        {
            if (IsDisposed) return;

            SocketService.OnDisconnected(new ConnectionEventArgs(connection));
        }

        internal void FireOnException(BaseSocketConnection connection, Exception ex)
        {
            if (IsDisposed) return;

            if (connection == null)
            {
                SocketService.OnException(new ExceptionEventArgs(connection, ex));
            }
            else
            {
                if (connection.Active)
                {
                    SocketService.OnException(new ExceptionEventArgs(connection, ex));
                }
            }
        }

        /// <summary>
        /// Begin send the data.
        /// </summary>
        internal void BeginSend(BaseSocketConnection connection, byte[] buffer, bool sentByServer)
        {
            if (IsDisposed) return;

            byte[] sendBuffer = null;
            try
            {
                if (connection.Active)
                {
                    if ( (connection.EventProcessing == EventProcessing.User) && (buffer.Length > MessageBufferSize) )
                    {
                        throw new MessageLengthException("Message length is greater than Host maximum message length.");
                    }

                    bool completedAsync = true;
                    int bufferSize = 0;
                    sendBuffer = BufferUtils.GetPacketBuffer(connection, buffer, ref bufferSize);

                    lock (connection.WriteQueue)
                    {
                        if (connection.WriteQueueHasItems)
                        {
                            //----- If the connection is sending, enqueue the message!
                            var message = new MessageBuffer(sendBuffer, bufferSize, sentByServer);
                            connection.WriteQueue.Enqueue(message);
                        }
                        else
                        {
                            connection.WriteOV.SetBuffer(sendBuffer, 0, bufferSize);
                            connection.WriteOV.UserToken = new WriteData(connection, sentByServer);

                            //----- If the connection is not sending, send the message!
                            if (connection.Stream != null)
                            {
                                //----- Ssl!
                                connection.Stream.BeginWrite(connection.WriteOV.Buffer, 0, bufferSize, new AsyncCallback(BeginSendCallbackSSL), new WriteData(connection, sentByServer));
                            }
                            else
                            {
                                //----- Socket!
                                completedAsync = connection.Socket.SendAsync(connection.WriteOV);
                            }
                            connection.WriteQueueHasItems = true;
                        }
                    }
                    sendBuffer = null;
                    if (!completedAsync)
                    {
                        BeginSendCallbackAsync(this, connection.WriteOV);
                    }
                }
            }
            catch (SocketException soex)
            {
                if ((soex.SocketErrorCode == SocketError.ConnectionReset)
                    || (soex.SocketErrorCode == SocketError.ConnectionAborted)
                    || (soex.SocketErrorCode == SocketError.NotConnected)
                    || (soex.SocketErrorCode == SocketError.Shutdown)
                    || (soex.SocketErrorCode == SocketError.Disconnecting))
                {
                    connection.BeginDisconnect();
                }
                else
                {
                    FireOnException(connection, soex);
                }
            }
            catch (Exception ex)
            {
                FireOnException(connection, ex);
            }

            if (sendBuffer != null)
            {
                BufferManager.ReturnBuffer(sendBuffer);
            }
        }

        private void BeginSendCallbackSSL(IAsyncResult ar)
        {
            switch (_callbackThreadType)
            {
                case CallbackThreadType.WorkerThread:
                    ThreadPool.QueueUserWorkItem(BeginSendCallbackSSLP, ar);
                    break;
                case CallbackThreadType.IOThread:
                    BeginSendCallbackSSLP(ar);
                    break;
            }
        }

        private void BeginSendCallbackSSLP(object state)
        {
            if (!IsDisposed)
            {

                IAsyncResult ar = null;
                WriteData writeData = null;
                BaseSocketConnection connection = null;
                bool sentByServer = false;

                try
                {

                    ar = (IAsyncResult)state;
                    writeData = (WriteData)ar.AsyncState;

                    connection = writeData.Connection;
                    sentByServer = writeData.SentByServer;

                    writeData.Connection = null;

                    if (connection.Active)
                    {

                        //----- Ssl!
                        connection.Stream.EndWrite(ar);
                        connection.SetConnectionData(0, connection.WriteOV.Count);

                        BufferManager.ReturnBuffer(connection.WriteOV.Buffer);

                        FireOnSent(connection, sentByServer);

                        if (connection.Active)
                        {

                            lock (connection.WriteQueue)
                            {

                                if (connection.WriteQueue.Count > 0)
                                {

                                    MessageBuffer messageBuffer = connection.WriteQueue.Dequeue();

                                    connection.WriteOV.SetBuffer(messageBuffer.Buffer, 0, messageBuffer.Count);
                                    connection.WriteOV.UserToken = new WriteData(connection, messageBuffer.SentByServer);

                                    connection.Stream.BeginWrite(connection.WriteOV.Buffer, 0, messageBuffer.Count, new AsyncCallback(BeginSendCallbackSSL), new WriteData(connection, sentByServer));

                                }
                                else
                                {
                                    connection.WriteQueueHasItems = false;
                                }

                            }

                        }

                    }

                }
                catch (Exception ex)
                {
                    FireOnException(connection, ex);
                }

            }

        }

        private void BeginSendCallbackAsync(object sender, SocketAsyncEventArgs e)
        {
            switch (_callbackThreadType)
            {
                case CallbackThreadType.WorkerThread:
                    ThreadPool.QueueUserWorkItem(BeginSendCallbackAsyncP, e);
                    break;
                case CallbackThreadType.IOThread:
                    BeginSendCallbackAsyncP(e);
                    break;
            }
        }

        private void BeginSendCallbackAsyncP(object state)
        {
            if (!IsDisposed)
            {
                SocketAsyncEventArgs e = null;
                WriteData writeData = null;
                BaseSocketConnection connection = null;

                bool sentByServer = false;
                bool canReadQueue = true;

                try
                {

                    e = (SocketAsyncEventArgs)state;
                    writeData = (WriteData)e.UserToken;

                    connection = writeData.Connection;
                    sentByServer = writeData.SentByServer;

                    writeData.Connection = null;

                    if (connection.Active)
                    {
                        
                        if (e.SocketError == SocketError.Success)
                        {

                            connection.SetConnectionData(0, e.BytesTransferred);

                            if ((e.Offset + e.BytesTransferred) < e.Count)
                            {

                                //----- Continue to send until all bytes are sent!
                                e.SetBuffer(e.Offset + e.BytesTransferred, e.Count - e.BytesTransferred - e.Offset);

                                if (!connection.Socket.SendAsync(e))
                                {
                                    BeginSendCallbackAsync(this, e);
                                }

                                canReadQueue = false;

                            }
                            else
                            {

                                BufferManager.ReturnBuffer(e.Buffer);
                                e.SetBuffer(null, 0, 0);

                                FireOnSent(connection, sentByServer);

                            }

                        }
                        else
                        {

                            canReadQueue = false;

                            if ((e.SocketError == SocketError.ConnectionReset)
                                || (e.SocketError == SocketError.NotConnected)
                                || (e.SocketError == SocketError.Shutdown)
                                || (e.SocketError == SocketError.ConnectionAborted)
                                || (e.SocketError == SocketError.Disconnecting))
                            {
                                connection.BeginDisconnect();
                            }
                            else
                            {
                                FireOnException(connection, new SocketException((int)e.SocketError));
                            }

                        }

                        //----- Check Queue!
                        if (canReadQueue)
                        {

                            bool completedAsync = true;

                            if (connection.Active)
                            {

                                lock (connection.WriteQueue)
                                {

                                    if (connection.WriteQueue.Count > 0)
                                    {

                                        //----- If has items, send it!
                                        MessageBuffer sendMessage = connection.WriteQueue.Dequeue();

                                        e.SetBuffer(sendMessage.Buffer, 0, sendMessage.Count);
                                        e.UserToken = new WriteData(connection, sendMessage.SentByServer);

                                        completedAsync = connection.Socket.SendAsync(e);

                                    }
                                    else
                                    {
                                        connection.WriteQueueHasItems = false;
                                    }

                                }

                                if (!completedAsync)
                                {
                                    BeginSendCallbackAsync(this, e);
                                }

                            }

                        }

                    }

                }
                catch (SocketException soex)
                {

                    if ((soex.SocketErrorCode == SocketError.ConnectionReset)
                        || (e.SocketError == SocketError.NotConnected)
                        || (soex.SocketErrorCode == SocketError.Shutdown)
                        || (soex.SocketErrorCode == SocketError.ConnectionAborted)
                        || (soex.SocketErrorCode == SocketError.Disconnecting))
                    {
                        connection.BeginDisconnect();
                    }
                    else
                    {
                        FireOnException(connection, soex);
                    }

                }
                catch (Exception ex)
                {
                    FireOnException(connection, ex);
                }

            }

        }

        /// <summary>
        /// Receive data from connetion.
        /// </summary>
        internal void BeginReceive(BaseSocketConnection connection)
        {
            if (IsDisposed) return;

            byte[] readMessage = null;
            try
            {
                if (connection.Active)
                {
                    var completedAsync = true;

                    lock (connection.SyncReadPending)
                    {
                        if (!connection.ReadPending)
                        {
                            //----- if the connection is not receiving, start the receive!
                            if (connection.EventProcessing == EventProcessing.User)
                            {
                                readMessage = BufferManager.TakeBuffer(MessageBufferSize);
                            }
                            else
                            {
                                readMessage = BufferManager.TakeBuffer(2048);
                            }

                            connection.ReadOV.SetBuffer(readMessage, 0, readMessage.Length);
                            connection.ReadOV.UserToken = connection;

                            if (connection.Stream != null)
                            {
                                //----- Ssl!
                                connection.Stream.BeginRead(connection.ReadOV.Buffer, 0, readMessage.Length, BeginReadCallbackSSL, connection);
                            }
                            else
                            {
                                completedAsync = connection.Socket.ReceiveAsync(connection.ReadOV);
                            }
                            connection.ReadPending = true;
                        }
                    }

                    if (!completedAsync)
                    {
                        BeginReadCallbackAsync(this, connection.ReadOV);
                    }

                    readMessage = null;
                }
            }
            catch (SocketException soex)
            {
                if ((soex.SocketErrorCode == SocketError.ConnectionReset)
                    || (soex.SocketErrorCode == SocketError.NotConnected)
                    || (soex.SocketErrorCode == SocketError.ConnectionAborted)
                    || (soex.SocketErrorCode == SocketError.Shutdown)
                    || (soex.SocketErrorCode == SocketError.Disconnecting))
                {
                    connection.BeginDisconnect();
                }
                else
                {
                    FireOnException(connection, soex);
                }
            }
            catch (Exception ex)
            {
                FireOnException(connection, ex);
            }

            if (readMessage != null)
            {
                BufferManager.ReturnBuffer(readMessage);
            }
        }


        private void BeginReadCallbackSSL(IAsyncResult ar)
        {
            switch (_callbackThreadType)
            {
                case CallbackThreadType.WorkerThread:
                    ThreadPool.QueueUserWorkItem(BeginReadCallbackSSLP, ar);
                    break;
                case CallbackThreadType.IOThread:
                    BeginReadCallbackSSLP(ar);
                    break;
            }
        }

        private void BeginReadCallbackSSLP(object state)
        {
            if (IsDisposed) return;

            var ar = (IAsyncResult) state;
            var connection = (BaseSocketConnection) ar.AsyncState;
            try
            {
                if (connection.Active)
                {
                    var readBytes = connection.Stream.EndRead(ar);
                    connection.SetConnectionData(readBytes, 0);

                    if (readBytes > 0)
                    {
                        ReadFromConnection(connection, readBytes);
                    }
                    else
                    {
                        connection.BeginDisconnect();
                    }
                }
            }
            catch (Exception ex)
            {
                FireOnException(connection, ex);
            }
        }

        private void BeginReadCallbackAsync(object sender, SocketAsyncEventArgs e)
        {
            switch (_callbackThreadType)
            {
                case CallbackThreadType.WorkerThread:
                    ThreadPool.QueueUserWorkItem(BeginReadCallbackAsyncP, e);
                    break;
                case CallbackThreadType.IOThread:
                    BeginReadCallbackAsyncP(e);
                    break;
            }
        }

        private void BeginReadCallbackAsyncP(object state)
        {
            if (IsDisposed) return;

            var e = (SocketAsyncEventArgs)state;
            var connection = (BaseSocketConnection)e.UserToken;

            try
            {
                if (!connection.Active) return;
                switch (e.SocketError)
                {
                    case SocketError.Success:
                        connection.SetConnectionData(e.BytesTransferred, 0);
                        if (e.BytesTransferred > 0)
                        {
                            ReadFromConnection(connection, e.BytesTransferred);
                        }
                        else
                        {
                            //----- Is has no data to read then the connection has been terminated!
                            connection.BeginDisconnect();
                        }
                        break;
                    case SocketError.Disconnecting:
                    case SocketError.ConnectionAborted:
                    case SocketError.Shutdown:
                    case SocketError.NotConnected:
                    case SocketError.ConnectionReset:
                        connection.BeginDisconnect();
                        break;
                    default:
                        FireOnException(connection, new SocketException((int)e.SocketError));
                        break;
                }
            }
            catch (SocketException soex)
            {
                if ((soex.SocketErrorCode == SocketError.ConnectionReset)
                    || (soex.SocketErrorCode == SocketError.NotConnected)
                    || (soex.SocketErrorCode == SocketError.Shutdown)
                    || (soex.SocketErrorCode == SocketError.ConnectionAborted)
                    || (soex.SocketErrorCode == SocketError.Disconnecting))
                {
                    connection.BeginDisconnect();
                }
                else
                {
                    FireOnException(connection, soex);
                }
            }
            catch (Exception ex)
            {
                FireOnException(connection, ex);
            }
        }

        private void ReadFromConnection(BaseSocketConnection connection, int readBytes)
        {
            var onePacketFound = false;
            var remainingBytes = 0;
            var e = connection.ReadOV;

            switch (connection.DelimiterType)
            {
                case DelimiterType.None:
                    //----- Message with no delimiter!
                    remainingBytes = ReadMessageWithNoDelimiter(connection, e, readBytes);
                    break;
                case DelimiterType.MessageTailExcludeOnReceive:
                case DelimiterType.MessageTailIncludeOnReceive:
                    //----- Message with tail!
                    remainingBytes = ReadMessageWithTail(connection, e, readBytes, ref onePacketFound);
                    break;
            }

            if (remainingBytes == 0)
            {
                e.SetBuffer(0, e.Buffer.Length);
            }
            else
            {
                if (!onePacketFound)
                {
                    e.SetBuffer(remainingBytes, e.Buffer.Length - remainingBytes);
                }
                else
                {
                    var readMessage = connection.BaseHost.BufferManager.TakeBuffer(MessageBufferSize);
                    Buffer.BlockCopy(e.Buffer, e.Offset, readMessage, 0, remainingBytes);

                    connection.BaseHost.BufferManager.ReturnBuffer(e.Buffer);
                    e.SetBuffer(null, 0, 0);
                    e.SetBuffer(readMessage, remainingBytes, readMessage.Length - remainingBytes);
                }
            }

            if (!connection.Active) return;
            //----- Read!
            var completedAsync = true;

            if (connection.Stream != null)
            {
                connection.Stream.BeginRead(e.Buffer, 0, e.Count, BeginReadCallbackSSL, connection);
            }
            else
            {
                completedAsync = connection.Socket.ReceiveAsync(e);
            }

            if (!completedAsync)
            {
                BeginReadCallbackAsync(this, e);
            }
        }

        private int ReadMessageWithNoDelimiter(BaseSocketConnection connection, SocketAsyncEventArgs e, int readBytes)
        {
            var rawBuffer = BufferUtils.GetRawBuffer(e.Buffer, readBytes);
            FireOnReceived(connection, rawBuffer);
            return 0;
        }

        private int ReadMessageWithTail(BaseSocketConnection connection, SocketAsyncEventArgs e, int readBytes, ref bool onePacketFound)
        {
            var delimiter = connection.Delimiter;
            var delimiterSize = delimiter.Length;
            var remainingBytes = readBytes + e.Offset;
            var bufferLength = e.Buffer.Length;
            var buffer = e.Buffer;
            var offsetToFind = 0;
            bool readPacket;

            do
            {
                var packetFound = false;
                while (offsetToFind < bufferLength)
                {
                    offsetToFind = Array.IndexOf(buffer, delimiter[0], offsetToFind);

                    if (offsetToFind == -1)
                    {
                        break;
                    }

                    if (delimiterSize == 1)
                    {
                        offsetToFind++;
                        packetFound = true;
                        break;
                    }

                    packetFound = true;
                    for (var i = 1; i < delimiterSize; i++)
                    {
                        offsetToFind++;

                        if (buffer[offsetToFind] == delimiter[i]) 
                            continue;
                        packetFound = false;
                        break;
                    }

                    if (packetFound)
                    {
                        break;
                    }
                }

                if (packetFound)
                {
                    onePacketFound = true;

                    var rawBuffer = BufferUtils.GetRawBufferWithTail(connection, e, offsetToFind, delimiterSize);
                    rawBuffer = CryptUtils.DecryptData(connection, rawBuffer, MessageBufferSize);

                    offsetToFind += 1;
                    remainingBytes -= (offsetToFind - e.Offset);

                    e.SetBuffer(offsetToFind, bufferLength - offsetToFind);

                    FireOnReceived(connection, rawBuffer);

                    readPacket = remainingBytes != 0;
                }
                else
                {
                    readPacket = false;
                }

            } while (readPacket);

            return remainingBytes;
        }

        /// <summary>
        /// Begin disconnect the connection
        /// </summary>
        internal void BeginDisconnect(BaseSocketConnection connection)
        {
            if (IsDisposed) return;
            if (!connection.Active) return;
            
            try
            {
                var e = new SocketAsyncEventArgs();
                e.Completed += BeginDisconnectCallbackAsync;
                e.UserToken = connection;

                if (!connection.Socket.DisconnectAsync(e))
                {
                    BeginDisconnectCallbackAsync(this, e);
                }
            }
            catch (Exception ex)
            {
                FireOnException(connection, ex);
            }
        }

        private void BeginDisconnectCallbackAsync(object sender, SocketAsyncEventArgs e)
        {
            if (IsDisposed) return;
            
            BaseSocketConnection connection = null;
            try
            {
                connection = (BaseSocketConnection) e.UserToken;

                e.Completed -= BeginDisconnectCallbackAsync;
                e.UserToken = null;
                e.Dispose();

                if (connection.Active)
                {
                    lock (connection.SyncActive)
                    {
                        CloseConnection(connection);
                        FireOnDisconnected(connection);
                    }
                }
            }
            finally
            {
                DisposeConnection(connection);
                RemoveSocketConnection(connection);
            }
        }

        #region Abstract Methods

        internal abstract void BeginReconnect(ClientSocketConnection connection);
        internal abstract void BeginSendToAll(ServerSocketConnection connection, byte[] buffer, bool includeMe);
        internal abstract void BeginSendTo(BaseSocketConnection connectionTo, byte[] buffer);

        #endregion

        public ISocketConnection[] GetConnections()
        {
            if (IsDisposed) return null;

            return GetSocketConnections();
        }

        public ISocketConnection GetConnectionById(long connectionId)
        {
            if (IsDisposed) return null;

            return GetSocketConnectionById(connectionId);
        }

        #endregion


        internal long GetConnectionId()
        {
            return Interlocked.Increment(ref _connectionId);
        }

        internal void AddSocketConnection(BaseSocketConnection socketConnection)
        {
            if (IsDisposed) return;

            _socketConnectionsSync.EnterWriteLock();
            try
            {
                _socketConnections.Add(socketConnection.ConnectionId, socketConnection);
                socketConnection.WriteOV.Completed += BeginSendCallbackAsync;
                socketConnection.ReadOV.Completed += BeginReadCallbackAsync;
            }
            finally
            {
                _socketConnectionsSync.ExitWriteLock();
            }
        }

        internal void RemoveSocketConnection(BaseSocketConnection socketConnection)
        {
            if (IsDisposed) return;
            if (socketConnection == null) return;

            _socketConnectionsSync.EnterWriteLock();
            try
            {
                _socketConnections.Remove(socketConnection.ConnectionId);
            }
            finally
            {
                if (_socketConnections.Count <= 0)
                {
                    _waitConnectionsDisposing.Set();
                }
                _socketConnectionsSync.ExitWriteLock();
            }
        }

        /// <summary>
        /// Initializes the connection
        /// </summary>
        /// <param name="connection"></param>
        internal virtual void InitializeConnection(BaseSocketConnection connection)
        {
            if (IsDisposed) return;

            switch (connection.EventProcessing)
            {
                case EventProcessing.None:
                    if (InitializeConnectionProxy(connection))
                    {
                        FireOnConnected(connection);
                    }
                    else
                    {
                        if (InitializeConnectionEncrypt(connection))
                        {
                            FireOnConnected(connection);
                        }
                        else
                        {
                            connection.EventProcessing = EventProcessing.User;
                            FireOnConnected(connection);
                        }
                    }
                    break;
                case EventProcessing.Proxy:
                    if (InitializeConnectionEncrypt(connection))
                    {
                        FireOnConnected(connection);
                    }
                    else
                    {
                        connection.EventProcessing = EventProcessing.User;
                        FireOnConnected(connection);
                    }
                    break;
                case EventProcessing.Encrypt:
                    connection.EventProcessing = EventProcessing.User;
                    FireOnConnected(connection);
                    break;
            }
        }


        internal BaseSocketConnection[] GetSocketConnections()
        {
            BaseSocketConnection[] items = null;

            if (!IsDisposed)
            {
                _socketConnectionsSync.EnterReadLock();

                try
                {
                    items = new BaseSocketConnection[_socketConnections.Count];
                    _socketConnections.Values.CopyTo(items, 0);
                }
                finally
                {
                    _socketConnectionsSync.ExitReadLock();
                }
            }

            return items;
        }

        internal BaseSocketConnection GetSocketConnectionById(long connectionId)
        {
            BaseSocketConnection item = null;

            if (!IsDisposed)
            {
                _socketConnectionsSync.EnterReadLock();

                try
                {
                    item = _socketConnections[connectionId];
                }
                finally
                {
                    _socketConnectionsSync.ExitReadLock();
                }
            }

            return item;
        }

        internal void DisposeConnection(BaseSocketConnection connection)
        {
            if (IsDisposed) return;
            if (connection == null) return;

            if (connection.WriteOV != null && connection.WriteOV.Buffer != null)
            {
                BufferManager.ReturnBuffer(connection.WriteOV.Buffer);
            }

            if (connection.ReadOV != null && connection.ReadOV.Buffer != null)
            {
                BufferManager.ReturnBuffer(connection.ReadOV.Buffer);
            }

            connection.Dispose();
        }

        private bool InitializeConnectionProxy(BaseSocketConnection connection)
        {
            var result = false;

            if (!IsDisposed)
            {
                if (connection.BaseCreator is SocketConnector)
                {
                    if (((SocketConnector)connection.BaseCreator).ProxyInfo != null)
                    {
                        connection.EventProcessing = EventProcessing.Proxy;
                        result = true;
                    }
                }
            }
            return result;
        }

        private bool InitializeConnectionEncrypt(BaseSocketConnection connection)
        {
            var result = false;

            if (!IsDisposed)
            {
                var cryptService = connection.BaseCreator.CryptoService;
                if ((cryptService != null) && (connection.EncryptionType != EncryptionType.None))
                {
                    connection.EventProcessing = EventProcessing.Encrypt;
                    result = true;
                }
            }
            return result;
        }

        private void CloseConnection(BaseSocketConnection connection)
        {
            if (IsDisposed) return;

            connection.Active = false;
            connection.Socket.Shutdown(SocketShutdown.Send);

            lock (connection.WriteQueue)
            {
                if (connection.WriteQueue.Count <= 0) return;
                
                for (var i = 1; i <= connection.WriteQueue.Count; i++)
                {
                    var message = connection.WriteQueue.Dequeue();

                    if (message != null)
                    {
                        BufferManager.ReturnBuffer(message.Buffer);
                    }
                }
            }
        }

        private void CheckSocketConnections(Object stateInfo)
        {
            if (IsDisposed) return;

            _idleTimer.Change(Timeout.Infinite, Timeout.Infinite);
            try
            {
                //----- Get connections!
                var items = GetSocketConnections();

                if (items != null)
                {
                    var loopSleep = 0;
                        
                    foreach (var cnn in items)
                    {
                        if (IsDisposed)
                        {
                            break;
                        }
                            
                        try
                        {
                            if (cnn != null)
                            {
                                //----- Check the idle timeout!
                                if (DateTime.Now > (cnn.LastAction.AddMilliseconds(IdleTimeOutValue)))
                                {
                                    cnn.BeginDisconnect();
                                }
                            }
                        }
                        finally
                        {
                            ThreadUtils.LoopSleep(ref loopSleep);
                        }
                    }
                }
            }
            finally
            {
                if (!IsDisposed)
                {
                    //----- Restart the timer event!
                    _idleTimer.Change(IdleCheckInterval, IdleCheckInterval);
                }
            }
            GC.Collect();
        }

        protected void AddCreator(BaseSocketConnectionCreator creator)
        {
            if (IsDisposed) return;

            lock (_socketCreators)
            {
                _socketCreators.Add(creator);
            }
        }

        protected void RemoveCreator(BaseSocketConnectionCreator creator)
        {
            if (IsDisposed) return;
           
            lock (_socketCreators)
            {
                _socketCreators.Remove(creator);

                if (_socketCreators.Count <= 0)
                {
                    _waitCreatorsDisposing.Set();
                }
            }
        }

        protected BaseSocketConnectionCreator[] GetSocketCreators()
        {
            if (IsDisposed) return null;

            lock (_socketCreators)
            {
                var items = new BaseSocketConnectionCreator[_socketCreators.Count];
                _socketCreators.CopyTo(items, 0);
                return items;
            }
        }

        private void OnConnected(BaseSocketConnection connection)
        {
            if (IsDisposed) return;
            if (!connection.Active) return;

            try
            {
                switch (connection.EventProcessing)
                {
                    case EventProcessing.Encrypt:
                        switch (connection.EncryptionType)
                        {
                            case EncryptionType.Rijndael:
                                if (connection.Host.HostType == HostType.Client)
                                {
                                    //----- Generate client asymmetric key pair (public and private)
                                    var clientKeyPair = new RSACryptoServiceProvider(2048);

                                    //----- Get the server public key
                                    RSACryptoServiceProvider serverPublicKey;
                                    connection.BaseCreator.CryptoService.OnSymmetricAuthenticate(connection, out serverPublicKey);

                                    //----- Generates symmetric algoritm
                                    var sa = CryptUtils.CreateSymmetricAlgoritm(connection.EncryptionType);

                                    //----- Adjust connection cryptors
                                    connection.Encryptor = sa.CreateEncryptor();
                                    connection.Decryptor = sa.CreateDecryptor();

                                    //----- Create authenticate message
                                    var am = new AuthenticationMessage
                                                 {
                                                     //----- Encrypt session IV and session Key with server public key
                                                     SessionIV = serverPublicKey.Encrypt(sa.IV, true), 
                                                     SessionKey = serverPublicKey.Encrypt(sa.Key, true),
                                                     //----- Encrypt client public key with symmetric algorithm
                                                     ClientKey = CryptUtils.EncryptDataForAuthenticate(connection.Encryptor, Encoding.UTF8.GetBytes(clientKeyPair.ToXmlString(false))), 
                                                     Data = new byte[32]
                                                 };

                                    //----- Create hash salt!
                                    RandomNumberGenerator.Create().GetBytes(am.Data);

                                    var m = new MemoryStream();

                                    //----- Create a sign with am.SourceKey, am.SessionKey and am.Data (salt)!
                                    m.Write(am.SessionKey, 0, am.SessionKey.Length);
                                    m.Write(am.ClientKey, 0, am.ClientKey.Length);
                                    m.Write(am.Data, 0, am.Data.Length);

                                    am.Sign = clientKeyPair.SignData(CryptUtils.EncryptDataForAuthenticate(connection.Encryptor, m.ToArray()), "SHA256");

                                    //----- Serialize authentication message
                                    m.SetLength(0);
                                    new BinaryFormatter().Serialize(m, am);

                                    connection.BeginSend(m.ToArray());

                                    m.Close();

                                    am.SessionIV.Initialize();
                                    am.SessionKey.Initialize();

                                    serverPublicKey.Clear();
                                    clientKeyPair.Clear();
                                }
                                else
                                {
                                    connection.BeginReceive();
                                }
                                break;
                            case EncryptionType.SSL:
                                if (connection.Host.HostType == HostType.Client)
                                {
                                    //----- Get SSL items
                                    X509Certificate2Collection certs = null;
                                    string serverName;
                                    var checkRevocation = true;

                                    connection.BaseCreator.CryptoService.OnSSLClientAuthenticate(connection, out serverName, ref certs, ref checkRevocation);

                                    //----- Authenticate SSL!
                                    var ssl = new SslStream(new NetworkStream(connection.Socket), true, connection.BaseCreator.ValidateServerCertificateCallback);

                                    if (certs == null)
                                    {
                                        ssl.BeginAuthenticateAsClient(serverName, new AsyncCallback(SslAuthenticateCallback), new AuthenticateCallbackData(connection, ssl, HostType.Client));
                                    }
                                    else
                                    {
                                        ssl.BeginAuthenticateAsClient(serverName, certs, System.Security.Authentication.SslProtocols.Tls, checkRevocation, SslAuthenticateCallback, new AuthenticateCallbackData(connection, ssl, HostType.Client));
                                    }
                                }
                                else
                                {
                                    //----- Get SSL items!
                                    X509Certificate2 cert;
                                    bool clientAuthenticate;
                                    var checkRevocation = true;

                                    connection.BaseCreator.CryptoService.OnSSLServerAuthenticate(connection, out cert, out clientAuthenticate, ref checkRevocation);

                                    //----- Authneticate SSL!
                                    var ssl = new SslStream(new NetworkStream(connection.Socket));
                                    ssl.BeginAuthenticateAsServer(cert, clientAuthenticate, System.Security.Authentication.SslProtocols.Default, checkRevocation, SslAuthenticateCallback, new AuthenticateCallbackData(connection, ssl, HostType.Server));
                                }
                                break;
                        }
                        break;
                    case EventProcessing.Proxy:
                        var proxyInfo = ((SocketConnector) connection.BaseCreator).ProxyInfo;
                        var endPoint = ((SocketConnector) connection.BaseCreator).RemoteEndPoint;
                        var proxyBuffer = ProxyUtils.GetProxyRequestData(proxyInfo, endPoint);
                        connection.BeginSend(proxyBuffer);
                        break;
                }
            }
            catch (Exception ex)
            {
                FireOnException(connection, ex);
            }
        }

        private void OnSent(BaseSocketConnection connection)
        {
            if (IsDisposed) return;
            if (!connection.Active) return;

            try
            {
                switch (connection.EventProcessing)
                {
                    case EventProcessing.Encrypt:
                        if (connection.Host.HostType == HostType.Server)
                        {
                            connection.EventProcessing = EventProcessing.User;
                            FireOnConnected(connection);
                        }
                        else
                        {
                            connection.BeginReceive();
                        }
                        break;
                    case EventProcessing.Proxy:
                        connection.BeginReceive();
                        break;
                }
            }
            catch (Exception ex)
            {
                FireOnException(connection, ex);
            }
        }

        private void OnReceived(BaseSocketConnection connection, byte[] buffer)
        {
            if (IsDisposed) return;
            if (!connection.Active) return;

            try
            {
                switch (connection.EventProcessing)
                {
                    case EventProcessing.Encrypt:
                        if (connection.Host.HostType == HostType.Server)
                        {
                            //----- Deserialize authentication message
                            var m = new MemoryStream();
                            m.Write(buffer, 0, buffer.Length);
                            m.Position = 0;

                            var b = new BinaryFormatter();
                            AuthenticationMessage am;

                            try
                            {
                                am = (AuthenticationMessage) b.Deserialize(m);
                            }
                            catch
                            {
                                am = null;
                            }

                            if (am != null)
                            {
                                //----- Server private key
                                RSACryptoServiceProvider serverPrivateKey;
                                connection.BaseCreator.CryptoService.OnSymmetricAuthenticate(connection, out serverPrivateKey);

                                //----- Decrypt session Key and session IV with server private key
                                var sa = CryptUtils.CreateSymmetricAlgoritm(connection.Creator.EncryptionType);
                                sa.Key = serverPrivateKey.Decrypt(am.SessionKey, true);
                                sa.IV = serverPrivateKey.Decrypt(am.SessionIV, true);

                                //----- Adjust connection cryptors
                                connection.Encryptor = sa.CreateEncryptor();
                                connection.Decryptor = sa.CreateDecryptor();

                                //----- Verify sign
                                var clientPublicKey = new RSACryptoServiceProvider();
                                clientPublicKey.FromXmlString(Encoding.UTF8.GetString(CryptUtils.DecryptDataForAuthenticate(connection.Decryptor, am.ClientKey)));

                                m.SetLength(0);
                                m.Write(am.SessionKey, 0, am.SessionKey.Length);
                                m.Write(am.ClientKey, 0, am.ClientKey.Length);
                                m.Write(am.Data, 0, am.Data.Length);

                                am.SessionIV.Initialize();
                                am.SessionKey.Initialize();
                                am.ClientKey.Initialize();

                                if (clientPublicKey.VerifyData(CryptUtils.EncryptDataForAuthenticate(connection.Encryptor, m.ToArray()), "SHA256", am.Sign))
                                {
                                    am.Data = new byte[32];
                                    RandomNumberGenerator.Create().GetBytes(am.Data);

                                    am.SessionIV = null;
                                    am.SessionKey = null;
                                    am.ClientKey = null;
                                    am.Sign = serverPrivateKey.SignData(am.Data, "SHA256");

                                    m.SetLength(0);
                                    b.Serialize(m, am);

                                    BeginSend(connection, m.ToArray(), false);
                                }
                                else
                                {
                                    FireOnException(connection, new SymmetricAuthenticationException("Symmetric sign error."));
                                }

                                am.Sign.Initialize();
                                m.Close();

                                serverPrivateKey.Clear();
                                clientPublicKey.Clear();
                            }
                            else
                            {
                                FireOnException(connection, new SymmetricAuthenticationException("Symmetric sign error."));
                            }
                        }
                        else
                        {
                            //----- Deserialize authentication message
                            var m = new MemoryStream();
                            m.Write(buffer, 0, buffer.Length);
                            m.Position = 0;

                            AuthenticationMessage am;
                            var b = new BinaryFormatter();

                            try
                            {
                                am = (AuthenticationMessage) b.Deserialize(m);
                            }
                            catch
                            {
                                am = null;
                            }

                            if (am != null)
                            {
                                RSACryptoServiceProvider serverPublicKey;
                                connection.BaseCreator.CryptoService.OnSymmetricAuthenticate(connection, out serverPublicKey);

                                //----- Verify sign
                                if (serverPublicKey.VerifyData(am.Data, "SHA256", am.Sign))
                                {
                                    connection.EventProcessing = EventProcessing.User;
                                    FireOnConnected(connection);
                                }
                                else
                                {
                                    FireOnException(connection, new SymmetricAuthenticationException("Symmetric sign error."));
                                }

                                am.Data.Initialize();
                                am.Sign.Initialize();

                                serverPublicKey.Clear();
                            }
                            else
                            {
                                FireOnException(connection, new SymmetricAuthenticationException("Symmetric sign error."));
                            }

                            m.Close();
                        }
                        break;
                    case EventProcessing.Proxy:
                        var proxyInfo = ((SocketConnector) connection.BaseCreator).ProxyInfo;
                        ProxyUtils.GetProxyResponseStatus(proxyInfo, buffer);

                        if (proxyInfo.Completed)
                        {
                            InitializeConnection(connection);
                        }
                        else
                        {
                            var endPoint = ((SocketConnector) connection.BaseCreator).RemoteEndPoint;
                            var proxyBuffer = ProxyUtils.GetProxyRequestData(proxyInfo, endPoint);
                            connection.BeginSend(proxyBuffer);
                        }
                        break;
                }
            }
            catch (Exception ex)
            {
                FireOnException(connection, ex);
            }
        }

        private void SslAuthenticateCallback(IAsyncResult ar)
        {
            if (IsDisposed) return;

            BaseSocketConnection connection = null;
            SslStream stream;
            var completed = false;

            try
            {
                var callbackData = (AuthenticateCallbackData) ar.AsyncState;

                connection = callbackData.Connection;
                stream = callbackData.Stream;

                if (connection.Active)
                {
                    if (callbackData.HostType == HostType.Client)
                    {
                        stream.EndAuthenticateAsClient(ar);
                    }
                    else
                    {
                        stream.EndAuthenticateAsServer(ar);
                    }

                    if ((stream.IsSigned && stream.IsEncrypted))
                    {
                        completed = true;
                    }

                    connection.Stream = stream;

                    if (completed)
                    {
                        connection.EventProcessing = EventProcessing.User;
                        FireOnConnected(connection);
                    }
                    else
                    {
                        FireOnException(connection, new SSLAuthenticationException("Ssl authenticate is not signed or not encrypted."));
                    }
                }
            }
            catch (Exception ex)
            {
                FireOnException(connection, ex);
            }
        }

        #region Destructor

        protected override void Free(bool canAccessFinalizable)
        {
            if (_idleTimer != null)
            {
                _idleTimer.Change(Timeout.Infinite, Timeout.Infinite);
                _idleTimer.Dispose();
                _idleTimer = null;
            }

            if (_waitCreatorsDisposing != null)
            {
                _waitCreatorsDisposing.Set();
                _waitCreatorsDisposing.Close();
                _waitCreatorsDisposing = null;
            }

            if (_waitConnectionsDisposing != null)
            {
                _waitConnectionsDisposing.Set();
                _waitConnectionsDisposing.Close();
                _waitConnectionsDisposing = null;
            }

            if (_waitThreadsDisposing != null)
            {
                _waitThreadsDisposing.Set();
                _waitThreadsDisposing.Close();
                _waitThreadsDisposing = null;
            }

            if (_socketConnections != null)
            {
                _socketConnections.Clear();
                _socketConnections = null;
            }

            if (_socketCreators != null)
            {
                _socketCreators.Clear();
                _socketCreators = null;
            }

            if (BufferManager != null)
            {
                BufferManager.Clear();
                BufferManager = null;
            }

            _socketConnectionsSync = null;
            SocketService = null;
            Delimiter = null;
            DelimiterEncrypt = null;

            base.Free(canAccessFinalizable);
        }

        #endregion
    }
}